// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fidl/flat_ast.h>
#include <fidl/formatter.h>
#include <fidl/lexer.h>
#include <fidl/parser.h>
#include <fidl/source_file.h>
#include <fidl/tree_visitor.h>
#include <unittest/unittest.h>

#include <regex>

#include "examples.h"
#include "test_library.h"

namespace {

std::map<std::string, std::string> formatted_output_;

void InitializeContents() {
  for (auto element : Examples::map()) {
    TestLibrary library(element.first, element.second);
    std::unique_ptr<fidl::raw::File> ast;
    library.Parse(&ast);

    fidl::raw::FormattingTreeVisitor visitor;
    visitor.OnFile(ast);

    formatted_output_[element.first] = *visitor.formatted_output();
  }
}

// Tests that repeatedly applying the formatter results in no change.
bool idempotence_test() {
  BEGIN_TEST;

  for (auto element : formatted_output_) {
    TestLibrary library(element.first, element.second);
    std::unique_ptr<fidl::raw::File> ast;
    EXPECT_TRUE(library.Parse(&ast));

    fidl::raw::FormattingTreeVisitor visitor;
    visitor.OnFile(ast);

    EXPECT_STR_EQ(element.second.c_str(), visitor.formatted_output()->c_str(),
                  "Applying formatting multiple times produces different results");
  }

  END_TEST;
}

bool basic_formatting_rules_test() {
  BEGIN_TEST;

  std::regex trailing_ws(".*\\s+$");
  std::regex top_level_decl("^\\s*(?:struct|enum|union)\\s+.*");
  std::regex attribute("\\s*\\[[A-Za-z]+\\]\\s*");
  std::regex comment("\\s*//.*");

  // Break the output into lines
  for (auto element : formatted_output_) {
    std::stringstream ss(element.second);
    std::string line;
    std::vector<std::string> lines;

    while (std::getline(ss, line, '\n')) {
      lines.push_back(line);

      // RULE: No trailing whitespace
      ASSERT_FALSE(std::regex_search(line, trailing_ws), "Trailing whitespace found");

      // RULE: No tab characters
      ASSERT_TRUE(line.find_first_of('\t') == std::string::npos, "Tab character found");

      // RULE: 4 space indents (at least)
      ASSERT_TRUE(!isspace(line[0]) || line.find_first_of("    ") == 0, "<4 space indent found");
    }

    // RULE: Separate top-level declarations for struct, enum, and union with one
    // blank line.
    for (size_t i = 0; i < lines.size(); i++) {
      if (std::regex_search(lines[i], top_level_decl)) {
        // Just means there is a top-level decl on the first line.
        if (i == 0) {
          continue;
        }

        size_t line_to_check = i - 1;
        // Back up to before attributes and comments.
        while (std::regex_search(lines[line_to_check], attribute) ||
               std::regex_search(lines[line_to_check], comment)) {
          line_to_check--;
          ASSERT_GE(line_to_check, 0);
        }

        std::string full = lines[i - 1] + lines[i];
        ASSERT_EQ(lines[line_to_check].size(), 0, "No blank line found before top level decl");
      }
    }

    // RULE: End the file with exactly one newline (no blank lines at the end).
    ASSERT_NE(lines[lines.size() - 1].size(), 0, "No newline at EOF");
  }

  END_TEST;
}

bool golden_file_test() {
  BEGIN_TEST;
  std::string good_output;
  std::string formatted_bad_output;

  for (auto element : Examples::map()) {
    if (element.first.find("testdata/goodformat.test.fidl") != std::string::npos) {
      good_output = Examples::map()[element.first];
    } else if (element.first.find("testdata/badformat.fidl") != std::string::npos) {
      formatted_bad_output = formatted_output_[element.first];
    }
  }

  ASSERT_GT(good_output.size(), 0);
  ASSERT_GT(formatted_bad_output.size(), 0);

  ASSERT_STR_EQ(good_output.c_str(), formatted_bad_output.c_str(),
                "Formatting for badformat.fidl looks weird");
  END_TEST;
}

}  // namespace

BEGIN_TEST_CASE(formatter_tests)
InitializeContents();
RUN_TEST(idempotence_test)
RUN_TEST(basic_formatting_rules_test)
RUN_TEST(golden_file_test)
END_TEST_CASE(formatter_tests)
